package com.wzdigit.wms.wms.task;

import com.wzdigit.mes.basic.api.BasicDataAttrApi;
import com.wzdigit.mes.basic.api.BasicDataItemDetailApi;
import com.wzdigit.mes.basic.api.BasicSimpleCodeRuleApi;
import com.wzdigit.mes.basic.dto.DataItemDetailDTO;
import com.wzdigit.mes.basic.dto.DataitemAttrDto;
import com.wzdigit.wms.basic.domain.basic.WarehouseAttr;
import com.wzdigit.wms.basic.domain.task.TaskLog;
import com.wzdigit.wms.basic.domain.task.TaskLogDtl;
import com.wzdigit.wms.basic.domain.transfer.DocPreAloc;
import com.wzdigit.wms.basic.domain.transfer.GdnDtl;
import com.wzdigit.wms.basic.domain.transfer.PicktaskHeader;
import com.wzdigit.wms.basic.enums.GdnDtlUdf1Enum;
import com.wzdigit.wms.basic.enums.task.TaskLogStatusEnum;
import com.wzdigit.wms.basic.enums.task.TaskResultEnum;
import com.wzdigit.wms.basic.enums.task.TaskTypeEnum;
import com.wzdigit.wms.wms.service.basic.WarehouseAttrService;
import com.wzdigit.wms.wms.service.core.GdnCoreService;
import com.wzdigit.wms.wms.service.core.task.TaskLogDtlService;
import com.wzdigit.wms.wms.service.core.task.TaskLogService;
import com.wzdigit.wms.wms.service.core.transfer.DocPreAlocService;
import com.wzdigit.wms.wms.service.mid.IfPostbackService;
import com.wzdigit.wms.wms.service.outbound.PicktaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 创建拣货任务 定时任务
 */
@ConditionalOnExpression("${schedule.common.switch}")
@Component
@EnableScheduling
@Slf4j
public class CreatePicktaskSchedule implements SchedulingConfigurer {

    @Value("${schedule.create_picktask.start_flag}")
    private Boolean startScheduleFlag;

    @Value("${schedule.create_picktask.frequency}")
    private String cron;

    @Value("${schedule.common.create_name}")
    private String createName;

    @Value("${dataItem.orderType.gdn.code}")
    private String gdn;

    @Value("${dataItem.orderType.gdn.other_attr}")
    private String gdnAttr;

    @Value("${dataItem.warehouse_attr.warehouse}")
    private String warehouseCode;

    @Value("${dataItem.warehouse_attr.dtl.picktask_key}")
    private String picktaskKey;

    @Value("${dataItem.warehouse_attr.dtl.pick_rule.data_name}")
    private String pickRuleKey;

    @Value("${order_code.picktask.code}")
    private String picktaskCode;

    @Autowired
    IfPostbackService ifPostbackService;

    @Autowired
    TaskLogService taskLogService;

    @Autowired
    TaskLogDtlService taskLogDtlService;

    @Autowired
    PicktaskService picktaskService;

    @Autowired
    GdnCoreService gdnCoreService;

    @Autowired
    DocPreAlocService docPreAlocService;

    @Autowired
    WarehouseAttrService warehouseAttrService;

    @Reference(version = "${dubbo.consumers.basic.data.item.detail.api:1.0.0}", check = false, timeout = 300000)
    BasicDataItemDetailApi basicDataItemDetailApi;

    @Reference(version = "${dubbo.consumers.basic.data.attr.api:1.0.0}", check = false, timeout = 300000)
    BasicDataAttrApi basicDataAttrApi;

    @Reference(version = "${dubbo.consumers.basic.simple.code.rule.api:1.0.0}", check = false)
    BasicSimpleCodeRuleApi basicSimpleCodeRuleApi;



    public static final Integer commonFlag = 1;


    final Lock lock = new ReentrantLock();

    final Condition condition = lock.newCondition();

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        //根据CRON构建定时任务
        taskRegistrar.addCronTask(() -> {

            lock.lock();
            try {
                //定时任务逻辑
                log.info("定时任务:【创建拣货任务】执行");

                //定时任务开启，先写日志表
                Integer taskLogId = insertTaskLog();

                //拣货任务----start

                //执行生成拣货任务逻辑，并返回处理结果
                String loginName = createName;

                //获取发货单的单据类型
                List<DataItemDetailDTO> dataItemDetailDTOSList = basicDataItemDetailApi.getDataItemDetailByCode(gdn);

                if (dataItemDetailDTOSList.size() == 0) {
                    String message = "未在数据字典中查询到发货单的单据类型,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
                }

                //所有【出库单】的单据类型的ID，来自数据字典
                List<Integer> itemDtlIdList = dataItemDetailDTOSList.stream().map(s -> s.getDataItemDetailId()).collect(Collectors.toList());

                //查询数据字典的拓展属性
                //根据拓展属性过滤单据类型(是否生成拣货任务)need_pick_task = 1，且有效的 is_enabled = 1
                List<DataitemAttrDto> dataitemAttrDtoList = basicDataAttrApi.getDataitemAttrDtoListByItemDtlIdAndAttr(itemDtlIdList, gdnAttr, commonFlag.toString(), commonFlag);

                if (dataitemAttrDtoList.size() == 0) {
                    String message = "未在数据字典拓展属性中查询到符合条件的单据类型（need_pick_task = 1，且有效的 is_enabled = 1）,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "未查询到符合条件的单据类型");
                }

                List<Integer> allowIitemDtlIdList = dataitemAttrDtoList.stream().map(s -> s.getItemDtlId()).collect(Collectors.toList());

                //查询符合条件的【单据类型】集合
                List<String> gdnTypeList = dataItemDetailDTOSList.stream().filter(s -> allowIitemDtlIdList.contains(s.getDataItemDetailId())).map(s -> s.getItemValue()).collect(Collectors.toList());

                //根据符合条件的【单据类型】集合（need_pick_task = 1，且有效的 is_enabled = 1），pick_task_flag <> 1 查询符合条件的记录

                List<GdnDtl> gdnDtls = gdnCoreService.selectAllowCreatePickTask(gdnTypeList, commonFlag);
                gdnDtls = gdnDtls.stream().filter(s -> GdnDtlUdf1Enum.YES.code.equals(s.getUdf1())).collect(Collectors.toList());
                //拣货任务------end

                //过滤仓库条件------------------------------------------------start
                //仓库ID集合
                List<Integer> whsIdList = gdnDtls.stream().map(s -> s.getWhsId()).distinct().collect(Collectors.toList());

                //仓库满足条件：仓库扩展属性attr=need_pick_task,value=1
                if (whsIdList.size() == 0) {
                    String message = "未分库单据不能执行下发操作,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "未分库单据不能执行下发操作");
                }

                //查数据字典获取:仓库扩展属性(Stock_Extend_Attribute)
                List<DataItemDetailDTO> dataItemDetailDTOList = basicDataItemDetailApi.getDataItemDetailByCode(warehouseCode);

                //是否生成拣货任务
                List<DataItemDetailDTO> dataItemDetailDTOsList = dataItemDetailDTOList.stream().filter(s -> s.getItemName().equals(picktaskKey)).collect(Collectors.toList());

                if (dataItemDetailDTOsList.size() == 0) {
                    String message = "未在数据字典的【仓库扩展属性】中查询到【是否生成拣货任务】项,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "未查询到符合条件的仓库扩展属性配置");
                }
                DataItemDetailDTO itemDetailDTO = dataItemDetailDTOsList.get(0);

                //根据仓库ID集合查询【仓库扩展属性】:ID,启用,仓库扩展属性attr=need_pick_task,value=1
                List<WarehouseAttr> warehouseAttrList = warehouseAttrService.getPicktaskAttrList(whsIdList, commonFlag, itemDetailDTO.getItemValue(), commonFlag.toString());
                if (warehouseAttrList.size() == 0) {
                    String message = "发货单明细的仓库存在【仓库拓展属性】未配置【是否生成拣货任务】的标识,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "发货单明细的仓库存在【仓库拓展属性】未配置【是否生成拣货任务】的标识");
                }

                //符合条件的仓库ID集合
                List<Integer> conformWhsIdList = warehouseAttrList.stream().distinct().filter(s -> s.getWhsId() != null).map(s -> s.getWhsId()).collect(Collectors.toList());


                //过滤器符合仓库条件的明细
                gdnDtls = gdnDtls.stream().filter(s -> conformWhsIdList.contains(s.getWhsId())).collect(Collectors.toList());
                //过滤仓库条件--------------------------------------------------end


                //一次性获取拣货任务生成规则
                //从仓库扩展属性取出属性attr=pick_rule的【值】，即拣货规则
                //保存 仓库ID和对应的拣货规则
                List<Integer> whsIdList1 = gdnDtls.stream().map(s -> s.getWhsId()).distinct().collect(Collectors.toList());

                //先从数据字典取值：根据数据字典的【拣货规则】取数据
                List<DataItemDetailDTO> itemDetailDTOS = dataItemDetailDTOList.stream().filter(s -> s.getItemName().equals(pickRuleKey)).collect(Collectors.toList());
                if (itemDetailDTOS.size() == 0) {
                    String message = "发货单明细的仓库中含有扩展属性未配置【拣货规则】,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "仓库扩展属性未配置【拣货规则】");
                }
                DataItemDetailDTO detailDTO = itemDetailDTOS.get(0);

                //仓库扩展属性的值:【拣货规则】
                String itemValue = detailDTO.getItemValue();//pick_rule

                //仓库ID和对应的拣货规则
                List<WarehouseAttr> warehouseAttrs = warehouseAttrService.getByWhsIdListAndAttr(whsIdList1, itemValue, commonFlag);


                //int a = 0 / 0;
                //一次性根据规则获取所需辅助信息
                //a．若pick_rule=ByZone：通过用户-分工获取用户-库区的对照关系

                // 判断当前任务对应的拣货规则、辅助信息是否存在？
                //若拣货规则、辅助信息任一项不存在，则转向80，记录一笔失败日志

                //TODO 判断拣货规则+辅助信息存在？正常:抛异常出去
                if (warehouseAttrs.size() == 0) {
                    String message = "根据仓库未查询到【拣货规则】相关信息,任务结束";
                    log.error(message);

                    updateFailTaskLog(taskLogId, message);
                    return;
//                    Shift.fatal(StatusCode.BUSINESS_ERR, "根据仓库未查询到【拣货规则】相关信息");
                }


                Map<Long, List<GdnDtl>> headMap = gdnDtls.stream().collect(Collectors.groupingBy(s -> s.getGdnHeaderId()));

                for (Map.Entry<Long, List<GdnDtl>> entry : headMap.entrySet()) {

                    List<GdnDtl> gdnDtlList = entry.getValue();

                    Map<Integer, List<GdnDtl>> whsMap = gdnDtlList.stream().collect(Collectors.groupingBy(s -> s.getWhsId()));
                    for (Map.Entry<Integer, List<GdnDtl>> listEntry : whsMap.entrySet()) {

                        Integer whsId = listEntry.getKey();
                        //拣货任务号
                        String picktaskCode = basicSimpleCodeRuleApi.GenerateCode(this.picktaskCode);
                        //生成拣货任务单头
                        PicktaskHeader picktaskHeader = picktaskService.createNewPicktaskHeader(loginName, whsId, picktaskCode);

                        //生成拣货任务明细
                        List<GdnDtl> createGdnList = listEntry.getValue();
                        //根据发货明细ID查询预配表
                        List<Long> gdnDtlIdList = createGdnList.stream().map(s -> s.getGdnDtlId()).collect(Collectors.toList());
                        List<DocPreAloc> docPreAlocList = docPreAlocService.selectByDocDtlIdList(gdnDtlIdList);
                        //添加过滤，有效预配才生成
                        docPreAlocList = docPreAlocList.stream().filter(s -> s.getStatus() == commonFlag).collect(Collectors.toList());

                        Integer linNo = 1;
                        //根据预配表生成拣货明细
                        for (DocPreAloc docPreAloc : docPreAlocList) {

                            Integer rowAdd = picktaskService.addNewPickTaskDtl(loginName, picktaskHeader, linNo, docPreAloc);
                            log.info("成功新增{}条记录", rowAdd);

                            //同步更新发货明细或调拨单明细.pick_task_flag=1
                            Integer rowUpdate = gdnCoreService.updateDtlStatusByDtlId(docPreAloc.getDocDtlId(), commonFlag, loginName);
                            log.info("成功更新{}条记录", rowUpdate);

                            linNo++;
                        }
                        //写明细表
                        TaskLogDtl taskLogDtl = new TaskLogDtl();
                        taskLogDtl.setTaskLogId(taskLogId);
                        taskLogDtl.setCreateTime(new Date());
                        taskLogDtl.setCreateUser(createName);
                        taskLogDtl.setResult(TaskResultEnum.TASK_RESULT_SUCCESS.getCode());
//                        taskLogDtl.setMessage();


                        Integer rowAdd = taskLogDtlService.addOne(taskLogDtl);
                        log.info("成功新增{}条记录",rowAdd);
                    }
                }

                //TODO 测试代码，测试和投产记得删除
//                int a = 0 / 0;

                updateSuccessTaskLog(taskLogId,"定时任务执行正常结束");
                //将其返回结果更新日志表
                log.info(taskLogId.toString());

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, this.getCron());


    }

    /**
     * 任务成功执行方法
     * @param taskLogId
     * @param message
     */
    private void updateSuccessTaskLog(Integer taskLogId, String message) {
        Date date = new Date();
        TaskLog taskLog = new TaskLog();
        taskLog.setTaskLogId(taskLogId);
        taskLog.setMessage(message);
        taskLog.setEndTime(date);
        taskLog.setStatus(TaskLogStatusEnum.TASK_LOG_STATUS_END.getCode());
        taskLog.setResult(TaskResultEnum.TASK_RESULT_SUCCESS.getCode());

        taskLogService.updateOne(taskLog);
    }

    /**
     * 任务失败执行方法
     * @param taskLogId
     * @param message
     */
    private void updateFailTaskLog(Integer taskLogId, String message) {
        Date date = new Date();
        TaskLog taskLog = new TaskLog();
        taskLog.setTaskLogId(taskLogId);
        taskLog.setMessage(message);
        taskLog.setEndTime(date);
        taskLog.setStatus(TaskLogStatusEnum.TASK_LOG_STATUS_END.getCode());
        taskLog.setResult(TaskResultEnum.TASK_RESULT_FAIL.getCode());

        taskLogService.updateOne(taskLog);
    }

    /**
     * 定时任务开启，新建日志表
     *
     * @return
     */
    private Integer insertTaskLog() {
        TaskLog taskLog = new TaskLog();
        Date now = new Date();

        taskLog.setTaskId(now.getTime());//yc说写时间戳
        taskLog.setTaskType(TaskTypeEnum.TASK_TYPE_PICKTASK.getCode());
        taskLog.setBeginTime(now);
        taskLog.setStatus(TaskLogStatusEnum.TASK_LOG_STATUS_NEW.getCode());
        taskLog.setCreateUser(createName);
        taskLog.setCreateTime(now);

        return taskLogService.addOne(taskLog);
    }

    /**
     * 构造CRON表达式
     */
    public String getCron() {

        //若果为TRUE，表示执行定时任务
        if (!startScheduleFlag) {
            //返回不执行定时任务标识
            return ScheduledTaskRegistrar.CRON_DISABLED;
        }

        //TODO 后续若需要有户自定义【定时任务】停启的需求，则开启下面代码，且需要修改其判断标识---start
//        while (!startScheduleFlag) {
//
//            try {
//                condition.await();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
        //TODO 后续若需要有户自定义【定时任务】停启的需求，则开启下面代码，且需要修改其判断标识---end
        log.info("执行的cron表达式:{}", cron);

        CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(cron);


        Date now = new Date();
        Date next = cronSequenceGenerator.next(now);//下次执行时间,从当前时间算

        SimpleDateFormat sdf = new SimpleDateFormat("YYYY/MM/dd HH:mm:ss.SSS");
        String nextFormatTime = sdf.format(next);

        log.info("下次【创建拣货任务】的定时任务执行时间:{}", nextFormatTime);

        return cron;
    }

    /**
     * 设置定时任务运行标识
     */
    @Deprecated
    public void setTaskRunningState() {
        condition.signal();
    }
}